// Flyweight generator implementation
// Copyright 2006 (C) Ralph Thomas

#include <view/flyweight_generator.h>
#include <view/gtk/generatorview.h>
#include <view/gtk/flyweight/parent.h>
#include <util/concepts/list.h>
#include <gtk/os_utilities.hpp>
#include <binder.hpp>
#include <map> // for pair
#include <toolkit/selection.h>
#include <toolkit/application.h>

#include <iostream>
#include <sstream>

namespace view {
	void set_renderer_value( GtkCellRenderer* r, const std::string& property, const adobe::value_t& value ) {
		if (!r) return;
		GValue val = { 0, };
		//
		// We need to convert from adobe::value_t to GValue.
		//
		if (value.type() == typeid(double)) {
			g_value_init(&val, G_TYPE_DOUBLE);
			g_value_set_double(&val, value.get<double>());
		} else if (value.type() == typeid(std::string)) {
			g_value_init(&val, G_TYPE_STRING);
			g_value_set_string(&val, value.get<std::string>().c_str());
		} else throw std::runtime_error( std::string("Unable to convert type to GValue: ") + value.type().name() );
		//
		// Now set the property.
		//
		g_object_set_property( G_OBJECT(r), property.c_str(), &val );
		g_value_unset( &val );
	}
	typedef std::pair<unsigned int, unsigned int> item_range_t;
	typedef std::vector<unsigned int> item_list_t;
	//
	/// Trival row, column table layout.
	//
	struct table_layout_t {
		int	item_width_m;
		int item_height_m;
		int view_width_m;
		int view_height_m;

		table_layout_t() : item_width_m(10), item_height_m(10), view_width_m(100), view_height_m(100) {}
		
		void set_item_size(int width, int height) { item_width_m = width; item_height_m = height; }
		void set_view_size(int width, int height) { view_width_m = width; view_height_m = height; }
		GdkRectangle get_item_rect(unsigned int index) const {
			GdkRectangle r;
			r.x = (index % items_per_row()) * item_width_m;
			r.width = item_width_m;
			r.height = item_height_m;
			r.y = (index / items_per_row()) * item_height_m;
			return r;
		}
		unsigned int get_item_index(int x, int y) const {
			x /= item_width_m;
			y /= item_height_m;
			return y * (view_width_m / item_width_m) + x;
		}
		item_range_t get_visible_items(int xoff, int yoff) const {
			unsigned int start(get_item_index(xoff, yoff));
			return item_range_t(start, start + items_per_row() * ((view_height_m / item_height_m) + 1 ));
		}
		item_list_t get_visible_items_rect(int x, int y, int w, int h, unsigned int nitems) const {
			unsigned int leftx(x / item_width_m);
			unsigned int lefty(y / item_height_m);
			unsigned int rightx((x+w) / item_width_m);
			unsigned int bottomy((y+h) / item_height_m);
			item_list_t items;
			items.reserve((rightx - leftx + 1) * (bottomy - lefty + 1));
			for (unsigned int row = lefty; row <= bottomy; row++)
				for (unsigned int col = leftx; col <= rightx; col++) {
					unsigned int item = row * items_per_row() + col;
					if (item < nitems) items.push_back(item);
				}
			return items;
		}
		int get_height(unsigned int count) const {
			//
			// This is the height based on the view width and item
			// width.
			//
			int nrows = (count / items_per_row()) + ((count % items_per_row()) ? 1 : 0);
			return nrows * item_height_m;
		}
		inline unsigned int items_per_row() const {
			int ipr = view_width_m / item_width_m;
			if (ipr < 1) ipr = 1;
			return ipr;
		}
		inline unsigned int items_per_column() const {
			int ipc = view_height_m / item_height_m;
			if (ipc < 1) ipc = 1;
			return ipc;
		}
	};

	struct renderer_t {
		GtkCellRenderer*		renderer_m;
		GdkRectangle			bounds_m;
		adobe::binder_helper_t*	datapump_m;
		std::string				property_m;

		renderer_t( GtkCellRenderer* r ) : renderer_m(r), datapump_m(0) { g_object_ref(r); bounds_m.x = bounds_m.y = bounds_m.width = bounds_m.height = 0; }
		renderer_t() : renderer_m(0), datapump_m(0) { bounds_m.x = bounds_m.y = bounds_m.width = bounds_m.height = 0; }
		renderer_t( const renderer_t& rhs ) : renderer_m(rhs.renderer_m), datapump_m(0) { bounds_m.x = bounds_m.y = bounds_m.width = bounds_m.height = 0; }
		~renderer_t() {
			if (!renderer_m) return;
			if (g_object_is_floating( renderer_m )) g_object_ref_sink( renderer_m );
			else g_object_unref( renderer_m );
		}
		bool operator==( const GtkCellRenderer* r ) const {
			return r == renderer_m;
		}
		bool contains( int x, int y ) const {
			if (x < bounds_m.x || x > (bounds_m.x + bounds_m.width)) return false;
			if (y < bounds_m.y || y > (bounds_m.y + bounds_m.height)) return false;
			return true;
		}
	};

	struct fw_generator : public flyweight_parent {
		typedef adobe::implementation::signal_wrapper_one_t<gboolean,GdkEventExpose*>	paint_handler_t;
		typedef adobe::implementation::signal_wrapper_zero_t<void>						scroll_handler_t;
		typedef adobe::implementation::signal_wrapper_one_t<gboolean,GdkEventButton*>	click_handler_t;
		typedef adobe::implementation::signal_wrapper_one_t<gboolean,GdkEventScroll*>	scroll_wheel_handler_t;
		typedef adobe::implementation::signal_wrapper_zero_t<void>						select_all_handler_t;
		typedef adobe::implementation::signal_wrapper_zero_t<void>						unselect_all_handler_t;
		typedef adobe::implementation::signal_wrapper_two_t<gboolean,GtkMovementStep,gint>	move_handler_t;
		typedef adobe::implementation::signal_wrapper_zero_t<void>						activate_handler_t;
		typedef adobe::implementation::signal_wrapper_zero_t<void>						toggle_handler_t;
		typedef std::vector<renderer_t>					renderer_list_t;
		typedef std::vector<adobe::binder_helper_t*>	binder_list_t;
		typedef std::pair<GtkCellRenderer*, std::string>		index_property_t;
		typedef std::vector<index_property_t>			index_property_list_t;
		paint_handler_t									paint_handler_m;
		scroll_handler_t								scroll_handler_m;
		scroll_wheel_handler_t							scroll_wheel_handler_m;
		click_handler_t									press_handler_m;
		click_handler_t									release_handler_m;
		select_all_handler_t							select_all_handler_m;
		unselect_all_handler_t							unselect_all_handler_m;
		move_handler_t									move_handler_m;
		activate_handler_t								activate_handler_m;
		toggle_handler_t								toggle_handler_m;
		adobe::auto_ptr<GtkWidget>						control_m;
		adobe::auto_ptr<GtkWidget>						scrollbar_m;
		adobe::binder_helper_t							datapump_m;
		adobe::binder_helper_t							selection_datapump_m;
		util::concept::List								data_m;
		GtkAdjustment*									vertical_adjustment_m;
		renderer_list_t									renderers_m;
		binder_list_t									binders_m;
		index_property_list_t							indexes_m;
		adobe::sheet_t&									sheet_m;
		table_layout_t									layout_m;
		adobe::eve_t									child_eve_m;
		adobe::eve_t::iterator							root_view_m;
		adobe::name_t									substitute_m;
		bool											need_layout_m;
		int												screen_scroll_m;
		//
		// Used by the set method.
		//
		unsigned int									set_index_m;
		renderer_t										set_target_m;
		//
		// Selection management.
		//
		toolkit::selection_ptr_t						selection_m;
		//
		// Selection state of the current selection (by keyboard).
		//
		toolkit::selection_t::selected_state_t			previous_selected_state_m;
		//
		// Action to invoke on double click.
		//
		adobe::binder_ptr_t								action_m;

		fw_generator(adobe::sheet_t& sheet, adobe::name_t substitute, const adobe::value_t& datapump, const adobe::value_t& selection, const adobe::binder_ptr_t& action)
			: datapump_m(sheet), selection_datapump_m(sheet), sheet_m(sheet), substitute_m(substitute), need_layout_m(true), screen_scroll_m(0), set_index_m(0), previous_selected_state_m(toolkit::selection_t::kUnselected), action_m(action) {
			//
			// Initialize our adjustments (required for updating our scrollbar)
			//
			vertical_adjustment_m = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 50.0, 50.0, 0.0));
			//
			// Initialize our widgetry.
			//
			control_m.reset( mission_generator_view_new() );//gtk_drawing_area_new() );
			gtk_widget_add_events( control_m.get(), GDK_ALL_EVENTS_MASK );
			scrollbar_m.reset( gtk_vscrollbar_new(vertical_adjustment_m) );
			//
			// The scrollbar stole our vertical adjustment reference.
			//
			g_object_ref(vertical_adjustment_m);
			//
			// Insert the root view (which we use to get the item size) into
			// the layout.
			//
			adobe::eve_t::signal_suite_t signals;
			signals.eve_place_proc_m = boost::bind( &fw_generator::set_item_size, this, _1, _2 );
			adobe::eve_t::calculate_data_t cdata;
			cdata.placement_m = adobe::eve_t::place_row;
			cdata.horizontal().margin_m.first = cdata.horizontal().margin_m.second = 2;
			cdata.vertical().margin_m.first = 2;
			root_view_m = child_eve_m.add_view_element(
					adobe::eve_t::iterator(),
					adobe::eve_t::insert_element_t(
						cdata,
						true,
						adobe::dictionary_t(),
						signals)
					);
			//
			// Hook up to recieve events.
			//
			paint_handler_m.set_callback( boost::bind( &fw_generator::paint, this, _1 ) );
			paint_handler_m.connect( control_m.get(), "expose-event", true );
			press_handler_m.set_callback( boost::bind( &fw_generator::click, this, _1 ) );
			press_handler_m.connect( control_m.get(), "button-press-event" );
			release_handler_m.set_callback( boost::bind( &fw_generator::click, this, _1 ) );
			release_handler_m.connect( control_m.get(), "button-release-event" );
			scroll_wheel_handler_m.set_callback( boost::bind( &fw_generator::scroll, this, _1 ) );
			scroll_wheel_handler_m.connect( control_m.get(), "scroll-event" );
			scroll_handler_m.set_callback( boost::bind( &fw_generator::scroll_paint, this ) );
			scroll_handler_m.connect( vertical_adjustment_m, "value-changed" );
			select_all_handler_m.set_callback( boost::bind( &fw_generator::select_all, this ) );
			select_all_handler_m.connect( control_m.get(), "select_all" );
			unselect_all_handler_m.set_callback( boost::bind( &fw_generator::unselect_all, this ) );
			unselect_all_handler_m.connect( control_m.get(), "unselect_all" );
			move_handler_m.set_callback( boost::bind( &fw_generator::move, this, _1, _2 ) );
			move_handler_m.connect( control_m.get(), "move_cursor" );
			activate_handler_m.set_callback( boost::bind( &fw_generator::activate, this ) );
			activate_handler_m.connect( control_m.get(), "activate_cursor_item" );
			toggle_handler_m.set_callback( boost::bind( &fw_generator::toggle, this ) );
			toggle_handler_m.connect( control_m.get(), "toggle_cursor_item" );
			//
			// Connect to our data.
			//
			datapump_m.initialize( datapump, boost::bind( &fw_generator::update, this, _1 ), 0 );
			//
			// Connect to the selection object.
			//
			selection_datapump_m.initialize( selection, boost::bind( &fw_generator::update_selection, this, _1 ), 0 );
		}
		virtual ~fw_generator() {
			scroll_handler_m.disconnect();
			g_object_unref(vertical_adjustment_m);
			for (unsigned int i = 0; i < binders_m.size(); i++)
				delete binders_m[i];
			//
			// Due to destructor order we explicitly disconnect
			// our handlers here, before our widget goes down.
			//
			paint_handler_m.disconnect();
			press_handler_m.disconnect();
			release_handler_m.disconnect();
			scroll_wheel_handler_m.disconnect();
			select_all_handler_m.disconnect();
			unselect_all_handler_m.disconnect();
			move_handler_m.disconnect();
			activate_handler_m.disconnect();
			toggle_handler_m.disconnect();
		}
		virtual void adopt_renderer(GtkCellRenderer* r) { renderers_m.push_back(renderer_t(r)); }
		virtual void position_renderer(GtkCellRenderer* p, int w, int h, int x, int y) {
			renderer_list_t::iterator i = std::find( renderers_m.begin(), renderers_m.end(), p );
			if (i == renderers_m.end()) return;
			i->bounds_m.x = x; i->bounds_m.y = y; i->bounds_m.width = w; i->bounds_m.height = h;
		}
		virtual void adopt_datapump(GtkCellRenderer* r, const std::string& property, const adobe::value_t& datapump) {
			adobe::binder_helper_t* bh = new adobe::binder_helper_t(sheet_m);
			bh->initialize(datapump, boost::bind(&set_renderer_value, r, property, _1), 0);
			binders_m.push_back(bh);
			renderer_list_t::iterator i = std::find( renderers_m.begin(), renderers_m.end(), r );
			if (i == renderers_m.end()) return;
			i->datapump_m = bh;
			i->property_m = property;
		}
		virtual GtkWidget* get_widget() const { return control_m.get(); }
		virtual void redraw(unsigned int i) {
			GdkRectangle r(layout_m.get_item_rect(i));
			r.y -= (int)vertical_adjustment_m->value;
			gdk_window_invalidate_rect(control_m.get()->window, &r, false);
		}
		virtual void redraw_all() {
			GdkRectangle r;
			r.x = r.y = 0; r.width = layout_m.view_width_m; r.height = layout_m.view_height_m;
			gdk_window_invalidate_rect(control_m.get()->window, &r, false);
		}
		virtual void subscribe_index(GtkCellRenderer* r, const std::string& property) {
			indexes_m.push_back(std::make_pair(r, property));
		}
		virtual void set(const adobe::value_t& val) {
			if (!set_target_m.datapump_m) return;
			set_target_m.datapump_m->substitute( substitute_m, data_m.at(set_index_m) );
			set_target_m.datapump_m->set( val );
			//
			// Find this renderer's property.
			//
			set_renderer_value(set_target_m.renderer_m, set_target_m.property_m, val);
		}
		void place(const adobe::point_2d_t& pos, const adobe::extents_t& extents) {
			layout_m.set_view_size(extents.width(), extents.height());
			vertical_adjustment_m->page_size = extents.height();
			adobe::implementation::set_bounds_widget( control_m.get(), pos, extents );
			update_scrolling();
		}
	  private:
		void update_scrolling() {
			vertical_adjustment_m->upper = layout_m.get_height(data_m.size());
			gtk_adjustment_changed(vertical_adjustment_m);
			if (vertical_adjustment_m->value > vertical_adjustment_m->upper) {
				vertical_adjustment_m->value = 0;
				gtk_adjustment_value_changed(vertical_adjustment_m);
			}
		}
		void update(const adobe::value_t& val) {
			data_m = util::concept::get_list(val);
			gtk_widget_queue_draw(control_m.get());
			update_scrolling();
		}
		void update_selection(const adobe::value_t& val) {
			if (val.type() == typeid(toolkit::selection_ptr_t)) {
				selection_m = val.get<toolkit::selection_ptr_t>();
				gtk_widget_queue_draw(control_m.get());
			}
		}
		void set_item_size(const adobe::eve_t::calculate_data_t&, const adobe::eve_t::place_data_t& pdata) {
			layout_m.set_item_size(pdata.horizontal().length_m, pdata.vertical().length_m);
		}
		void draw_single_item(unsigned int index, int xoff, int yoff, GdkRectangle* expose_area, cairo_t* cr) {
			GdkRectangle item_bounds( layout_m.get_item_rect( index ) );
			item_bounds.x += xoff; item_bounds.y += yoff;
			//
			// Get the value that we're drawing.
			//
			adobe::value_t v(data_m.at(index));
			//
			// Draw the background of this item if it's selected.
			//
			if (selection_m) {
				toolkit::selection_t::selected_state_t selected(selection_m->is_selected(index));
				GtkWidget* w = control_m.get();
				if (selected == toolkit::selection_t::kSelected) {
					cairo_set_source_rgb(cr, w->style->bg[GTK_STATE_ACTIVE].red/65535.0,
						w->style->bg[GTK_STATE_ACTIVE].green/65535.0,
						w->style->bg[GTK_STATE_ACTIVE].blue/65535.0);
				} else if (selected == toolkit::selection_t::kPartial) {
					cairo_set_source_rgb(cr, w->style->bg[GTK_STATE_SELECTED].red/65535.0,
						w->style->bg[GTK_STATE_SELECTED].green/65535.0,
						w->style->bg[GTK_STATE_SELECTED].blue/65535.0);
				}
				if (selected != toolkit::selection_t::kUnselected) {
					const int l = item_bounds.x; const int r = item_bounds.x + layout_m.item_width_m;
					const int t = item_bounds.y; const int b = item_bounds.y + layout_m.item_height_m;
					cairo_new_path(cr);
					cairo_move_to(cr, l + 10, t);
					cairo_line_to(cr, r - 10, t);
					cairo_curve_to(cr, r - 10, t, r, t, r, t + 10);
					cairo_line_to(cr, r, b - 10);
					cairo_curve_to(cr, r, b - 10, r, b, r - 10, b);
					cairo_line_to(cr, l + 10, b);
					cairo_curve_to(cr, l + 10, b, l, b, l, b - 10);
					cairo_line_to(cr, l, t + 10);
					cairo_curve_to(cr, l, t + 10, l, t, l + 10, t);
					cairo_close_path(cr);
					cairo_fill(cr);
				}
			}
			//
			// Now we have to substitute the correct value into all
			// of the datapumps.
			//
			std::for_each(binders_m.begin(), binders_m.end(), boost::bind( &adobe::binder_helper_t::substitute, _1, substitute_m, v ));
			//
			// Set the indexes into renderers that requested them.
			//
			for (index_property_list_t::const_iterator i = indexes_m.begin(); i != indexes_m.end(); i++) {
				set_renderer_value(i->first, i->second, adobe::value_t(index));
			}
			//
			// Now we may draw all of the renderers.
			//
			for (renderer_list_t::const_iterator i = renderers_m.begin(); i != renderers_m.end(); i++) {
				GdkRectangle b = i->bounds_m;
				b.x += item_bounds.x;
				b.y += item_bounds.y;
				gtk_cell_renderer_render( i->renderer_m, control_m.get()->window, control_m.get(), &b, &b, expose_area, (GtkCellRendererState)0 );
			}
		}
		void draw_multiple_items(int x, int y, int w, int h) {
			item_list_t items = layout_m.get_visible_items_rect(x, y + (int)vertical_adjustment_m->value, w, h, data_m.size());
			GdkRectangle expose_area;
			int yoff = -(int)vertical_adjustment_m->value;
			expose_area.x = x;
			expose_area.y = y;
			expose_area.width = w; expose_area.height = h;
			//
			// Fill in the damaged area with a rectangle.
			//
			gdk_draw_rectangle( control_m.get()->window, control_m.get()->style->bg_gc[0], true, expose_area.x, expose_area.y, w, h );
			//
			// Create the cairo rendering context ONCE per redraw.
			//
			cairo_t* cr = gdk_cairo_create( control_m.get()->window );
			std::for_each( items.begin(), items.end(), boost::bind(&fw_generator::draw_single_item, this, _1, 0, yoff, &expose_area, cr) );
			cairo_destroy( cr );
		}
		void scroll_paint() {
			gdk_window_scroll(control_m.get()->window, 0, screen_scroll_m - ((int) vertical_adjustment_m->value));
			screen_scroll_m = (int) vertical_adjustment_m->value;
			return;
		}
		gboolean paint(GdkEventExpose* expose) {
			//
			// Make sure that we're layed out.
			//
			if (need_layout_m) {
				child_eve_m.evaluate( adobe::eve_t::evaluate_nested );
				update_scrolling();
				need_layout_m = false;
			}
			draw_multiple_items(expose->area.x, expose->area.y, expose->area.width, expose->area.height);
			return false;
		}
		gboolean scroll(GdkEventScroll* scroll) {
			if (layout_m.view_height_m >= vertical_adjustment_m->upper) return true;
			if (scroll->direction == GDK_SCROLL_UP) {
				vertical_adjustment_m->value -= vertical_adjustment_m->step_increment;
				if (vertical_adjustment_m->value < vertical_adjustment_m->lower)
					vertical_adjustment_m->value = vertical_adjustment_m->lower;
				gtk_adjustment_value_changed(vertical_adjustment_m);
				return true;
			} else if (scroll->direction == GDK_SCROLL_DOWN) {
				vertical_adjustment_m->value += vertical_adjustment_m->step_increment;
				gdouble upper = vertical_adjustment_m->upper - vertical_adjustment_m->page_size;
				if (vertical_adjustment_m->value > upper)
					vertical_adjustment_m->value = upper;
				gtk_adjustment_value_changed(vertical_adjustment_m);
				return true;
			}
			return false;
		}
		gboolean click(GdkEventButton* button) {
			//
			// Regardless of what happened, take keyboard focus.
			//
			gtk_widget_grab_focus(control_m.get());
			//
			// Invoke the action if it's a double click.
			//
			if (button->type == GDK_2BUTTON_PRESS) {
				activate();
				return true;
			}
			//
			// For now we ignore button releases.
			//
			if (button->type != GDK_BUTTON_PRESS) return false;
			//
			// Figure out where the click went and attempt
			// to forward it to the correct view.
			//
			unsigned int index = layout_m.get_item_index((int)button->x, (int)(button->y + vertical_adjustment_m->value));
			if (index >= data_m.size()) {
				if (selection_m) selection_m->unselect_all();
				gtk_widget_queue_draw(control_m.get());
				return true;
			}
			GdkRectangle r = layout_m.get_item_rect(index);
			r.y -= (int)vertical_adjustment_m->value;
			int x = (int)button->x - r.x;
			int y = (int)button->y - r.y;
			//
			// Find out which renderer the mouse went down in.
			//
			renderer_list_t::const_iterator i = std::find_if( renderers_m.begin(), renderers_m.end(), boost::bind( &renderer_t::contains, _1, x, y ) );
			if (i == renderers_m.end()) {
				select(index, button->state);
				return false;
			}
			x -= i->bounds_m.x; y -= i->bounds_m.y;
			//
			// We're not a tree view, so cell renderers can't lookup
			// the coordinates inside them. The rating renderer has
			// been hacked up so that if the parent isn't a tree
			// view it expects it's local coordinates to be in the
			// button event.
			//
			button->x = x; button->y = y;
			GdkRectangle bounds = i->bounds_m;
			//
			// Set up ourselves for a "set" call.
			//
			set_index_m = index;
			set_target_m = *i;
			if (button->button == 1)
				if (!gtk_cell_renderer_activate(i->renderer_m, (GdkEvent*)button, control_m.get(), "", &bounds, &bounds, (GtkCellRendererState)0)) {
					select(index, button->state);
				}
			set_target_m = renderer_t();
			return true;
		}
		void select(unsigned int index, unsigned int state) {
			//
			// If this is the currently selected index then do nothing.
			//
			adobe::value_t primary = selection_m->get_primary();
			unsigned int last_index(0);
			if (primary != adobe::value_t()) {
				last_index = selection_m->get_index(primary);
				if (last_index == index) return;
			}
			//
			// Get the state of this item.
			//
			previous_selected_state_m = selection_m->is_selected(index);
			//
			// If control or shift is down then don't unselect everything.
			//
			if (((state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == 0) && (selection_m->is_selected(index) == toolkit::selection_t::kUnselected))
				selection_m->unselect_all();
			//
			// If shift is down and primary is not empty then select a range,
			// otherwise just select a single item.
			//
			if (((state & GDK_SHIFT_MASK) != 0) && (primary != adobe::value_t()))
				selection_m->select_range(last_index, index);
			selection_m->set_state(index, toolkit::selection_t::kSelected);
			//
			// XXX: Currently we redraw everything, but it should be possible to
			// XXX: get a list (within our visible items) of the indices that
			// XXX: need invalidating.
			//
			gtk_widget_queue_draw(control_m.get());
		}
		void select_all() {
			if (selection_m) {
				selection_m->select_all();
				gtk_widget_queue_draw(control_m.get());
			}
		}
		void unselect_all() {
			if (selection_m) {
				selection_m->unselect_all();
				gtk_widget_queue_draw(control_m.get());
			}
		}
		gboolean move(GtkMovementStep step, gint amount) {
			if (selection_m) {
				if (step == GTK_MOVEMENT_DISPLAY_LINES) amount *= layout_m.items_per_row();
				else if (step == GTK_MOVEMENT_BUFFER_ENDS) {
					if (amount == -1) amount = -data_m.size();
					else amount = data_m.size();
				} else if (step == GTK_MOVEMENT_PAGES) {
					amount *= layout_m.items_per_column() * layout_m.items_per_row();
				}
				//
				// Inval the current primary selection if there is one.
				//
				adobe::value_t primary(selection_m->get_primary());
				unsigned int dest_index = 0;
				if (primary != adobe::value_t()) {
					unsigned int index = selection_m->get_index(primary);
					redraw(index);
					dest_index = index;
					if ((amount < 0) && (((unsigned int)(amount * -1)) > index)) dest_index = 0;
					else dest_index += amount;
					if (dest_index >= data_m.size()) dest_index = data_m.size() - 1;
					//
					// If we're moving to where we already are then don't
					// bother.
					//
					if (index == dest_index) {
						scroll_to_primary();
						return true;
					}
					//
					// Find out if control or shift are down. If control is down
					// then we leave the previous item selected. If shift is down
					// then we also select the range from the previous primary to
					// ourselves.
					//
					GdkModifierType state((GdkModifierType)0);
					gtk_get_current_event_state(&state);
					//
					// Restore the previous item to it's selection state before
					// we moved over it (if keyboard was the last navigation).
					//
					if ((state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == 0)
						selection_m->set_state(index, previous_selected_state_m);
					else
						redraw(index);
					if (state & GDK_SHIFT_MASK) {
						unsigned int start(index);
						unsigned int end(dest_index);
						if (start > end) std::swap(start, end);
						selection_m->select_range(start, end);
						for (unsigned int i = start; i < end; i++)
							redraw(i);
					}
					//
					// This is the index of where we're going to be, record it's
					// current state.
					//
					previous_selected_state_m = selection_m->is_selected(dest_index);
				}
				//
				// We can get the event and see if we need to select this one and
				// the previous one or if we need to select all of the ones in
				// between.
				//
				selection_m->set_state(dest_index, toolkit::selection_t::kSelected);
				redraw(dest_index);
				//
				// Find out where we are.
				//
				scroll_to_primary();
			}
			return true;
		}
		void activate() {
			if (action_m) toolkit::application::getInstance().executeInMain(boost::bind(&adobe::binder_t::invoke, action_m));
		}
		void toggle() {
		}
		void scroll_to_primary() {
			if (!selection_m) return;
			adobe::value_t primary = selection_m->get_primary(); 
			if (primary == adobe::value_t()) return;
			unsigned int index = selection_m->get_index(primary);
			redraw(index);
			GdkRectangle r = layout_m.get_item_rect(index);
			//
			// Ensure that it's visible.
			//
			if (r.y < vertical_adjustment_m->value) {
				vertical_adjustment_m->value = r.y;
				gtk_adjustment_value_changed(vertical_adjustment_m);
				return;
			}
			if ((r.y + r.height) > (layout_m.view_height_m + vertical_adjustment_m->value)) {
				vertical_adjustment_m->value = (r.y + r.height) - layout_m.view_height_m;
				gtk_adjustment_value_changed(vertical_adjustment_m);
				return;
			}
		}
	};
	adobe::widget_node_t flyweight_generator_factory(
			const adobe::dictionary_t&		parameters,
			const adobe::widget_node_t&		parent,
			const adobe::factory_token_t&	token,
			factory*						f)
	{
		//
		// We create a row to insert things into.
		//
		adobe::eve_t::calculate_data_t cdata;
		cdata.create_m = false;
		cdata.placement_m = adobe::eve_t::place_row;
		cdata.spacing_m.push_back(1);
		adobe::eve_t::iterator parent_row =
			token.layout_m.add_view_element( parent.eve_token_m,
					adobe::eve_t::insert_element_t(
						cdata,
						true,
						parameters,
						adobe::eve_t::signal_suite_t()
						)
					);
		//
		// See if we have a selection.
		//
		adobe::value_t selection;
		if (parameters.count(adobe::static_name_t("selection")))
			selection = parameters.at( adobe::static_name_t( "selection" ) );
		//
		// See if we have an action.
		//
		adobe::binder_ptr_t action;
		if (parameters.count(adobe::static_name_t("action")))
			action = parameters.get<adobe::binder_ptr_t>( adobe::static_name_t( "action" ) );
		//
		// Now create our views.
		//
		fw_generator* fwg = new fw_generator(token.sheet_m, parameters.get<adobe::name_t>( adobe::static_name_t( "iterator" ) ), parameters.at(adobe::static_name_t( "bind" )), selection, action);
		token.assemblage_m.delete_on_destruction(fwg);
		//
		// Push our layout engine into the factory.
		//
		factory_token_ptr_t child_token;
		child_token.layout_m = &fwg->child_eve_m;
		f->push( factory::factory_t( child_token, 0 ) );
		//
		// Insert ourselves into our parent's layout and into our
		// parent.
		//
		GtkWidget* tmp(fwg->control_m.get());
		adobe::insert( adobe::get_main_display(), parent.display_token_m, tmp );
		adobe::display_t::position_t overlay_token = token.overlay_m.insert( parent.overlay_token_m );
		//
		// If we have a style parameter then set the widget name.
		//
		if (parameters.count(adobe::static_name_t("style"))) {
			gtk_widget_set_name(tmp, parameters.get<std::string>(adobe::static_name_t("style")).c_str());
		}
		//
		// Get the minimum dimensions
		//
		adobe::extents_t extents;
		int min_width = 0, min_height = 0;
		if (parameters.count(adobe::static_name_t("min_width")))
			parameters.get(adobe::static_name_t("min_width"), min_width);
		if (parameters.count(adobe::static_name_t("min_height")))
			parameters.get(adobe::static_name_t("min_height"), min_height);
		extents.width() = min_width; extents.height() = min_height;
		//
		// Build the signals for the layout.
		//
		adobe::eve_t::signal_suite_t signals;
		signals.eve_calculate_proc_m = boost::bind( &eve_client::utility_widget_calculate, extents, _1 );
		eve_client::place_func_t placer = boost::bind( &fw_generator::place, fwg, _1, _2 );
		signals.eve_place_proc_m = boost::bind( &eve_client::utility_widget_place, placer, _1, _2, boost::ref( token.overlay_m ), overlay_token );
		//
		// Insert.
		//
		cdata.create_m = true;
		cdata.placement_m = adobe::eve_t::place_leaf;
		cdata.horizontal().alignment_m = adobe::eve_t::align_fill;
		cdata.vertical().alignment_m = adobe::eve_t::align_fill;
		token.layout_m.add_view_element( parent_row,
				adobe::eve_t::insert_element_t(
					cdata,
					false,
					adobe::dictionary_t(),
					signals
					)
				);
		//
		// Insert the scrollbar.
		//
		tmp = fwg->scrollbar_m.get();
		adobe::insert( adobe::get_main_display(), parent.display_token_m, tmp );
		signals.eve_calculate_proc_m = boost::bind( &eve_client::utility_widget_calculate, adobe::implementation::best_bounds_widget(tmp), _1 );
		placer = boost::bind( &adobe::implementation::set_bounds_widget, tmp, _1, _2 );
		overlay_token = token.overlay_m.insert( parent.overlay_token_m );
		signals.eve_place_proc_m = boost::bind( &eve_client::utility_widget_place, placer, _1, _2, boost::ref( token.overlay_m ), overlay_token );
		//
		// And into the layout.
		//
		cdata.horizontal().alignment_m = adobe::eve_t::align_right;
		token.layout_m.add_view_element( parent_row,
				adobe::eve_t::insert_element_t(
					cdata,
					false,
					adobe::dictionary_t(),
					signals
					)
				);
		//
		// We return quite a strange display token in our widget
		// node. The flyweight children use it to get direct
		// access to us (rather than going through adobe::insert).
		//
		return adobe::widget_node_t( parent.size_m, fwg->root_view_m, boost::any(static_cast<flyweight_parent*>(fwg)), overlay_token );
	}
}

